{
  "cells": [
    {
      "metadata": {
        "id": "-n3Rngfddxii"
      },
      "cell_type": "markdown",
      "source": [
        "This notebook is a basic tutorial that demonstrates how to make an actor prefab and put it into a simulation using Concordia."
      ]
    },
    {
      "metadata": {
        "id": "T6qSDTZtdeXk"
      },
      "cell_type": "markdown",
      "source": [
        "<a href=\"https://colab.research.google.com/github/google-deepmind/concordia/blob/main/examples/actor_development.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "metadata": {
        "id": "o4NhU-Z6cbop"
      },
      "cell_type": "code",
      "source": [
        "# @title Colab-specific setup (use a CodeSpace to avoid the need for this).\n",
        "try:\n",
        "  %env COLAB_RELEASE_TAG\n",
        "except:\n",
        "  pass  # Not running in colab.\n",
        "else:\n",
        "  %pip install --ignore-requires-python --requirement 'https://raw.githubusercontent.com/google-deepmind/concordia/main/examples/requirements.in' 'git+https://github.com/google-deepmind/concordia.git#egg=gdm-concordia'\n",
        "  %pip list"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "R8N4hGX5ci_d"
      },
      "cell_type": "code",
      "source": [
        "# @title Imports\n",
        "\n",
        "from collections.abc import Mapping\n",
        "import dataclasses\n",
        "from concordia.agents import entity_agent_with_logging\n",
        "from concordia.associative_memory import basic_associative_memory\n",
        "from concordia.components import agent as agent_components\n",
        "from concordia.contrib import language_models as language_model_utils\n",
        "from concordia.contrib.components.agent import situation_representation_via_narrative\n",
        "from concordia.document import interactive_document\n",
        "from concordia.language_model import language_model\n",
        "import concordia.prefabs.entity as entity_prefabs\n",
        "import concordia.prefabs.game_master as game_master_prefabs\n",
        "from concordia.prefabs.simulation import generic as simulation\n",
        "from concordia.typing import prefab as prefab_lib\n",
        "from concordia.utils import helper_functions\n",
        "from IPython import display\n",
        "import numpy as np\n",
        "import sentence_transformers"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "U_193A2aQbj7"
      },
      "cell_type": "code",
      "source": [
        "# @title Language Model Selection: provide key or select DISABLE_LANGUAGE_MODEL\n",
        "\n",
        "# By default this colab uses models via an external API so you must provide an\n",
        "# API key. TogetherAI offers open weights models from all sources.\n",
        "\n",
        "API_KEY = ''  # @param {type: 'string'}\n",
        "# See concordia/language_model/utils.py\n",
        "API_TYPE = 'openai'  # e.g. 'together_ai' or 'openai'.\n",
        "MODEL_NAME = (  # for API_TYPE = 'together_ai', we recommend MODEL_NAME = 'google/gemma-3-27b-it'\n",
        "    'gpt-5'\n",
        ")\n",
        "# To debug without spending money on API calls, set DISABLE_LANGUAGE_MODEL=True\n",
        "DISABLE_LANGUAGE_MODEL = False"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "NiRFDDYMPpd5"
      },
      "cell_type": "code",
      "source": [
        "# @title Use the selected language model\n",
        "\n",
        "# Note that it is also possible to use local models or other API models,\n",
        "# simply replace this cell with the correct initialization for the model\n",
        "# you want to use.\n",
        "\n",
        "if not DISABLE_LANGUAGE_MODEL and not API_KEY:\n",
        "  raise ValueError('API_KEY is required.')\n",
        "\n",
        "model = language_model_utils.language_model_setup(\n",
        "    api_type=API_TYPE,\n",
        "    model_name=MODEL_NAME,\n",
        "    api_key=API_KEY,\n",
        "    disable_language_model=DISABLE_LANGUAGE_MODEL,\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "KC0dbTwLci6k"
      },
      "cell_type": "code",
      "source": [
        "# @title Setup sentence encoder\n",
        "\n",
        "if DISABLE_LANGUAGE_MODEL:\n",
        "  embedder = lambda _: np.ones(3)\n",
        "else:\n",
        "  st_model = sentence_transformers.SentenceTransformer(\n",
        "      'sentence-transformers/all-mpnet-base-v2'\n",
        "  )\n",
        "  embedder = lambda x: st_model.encode(x, show_progress_bar=False)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "yN8NEgajci3_"
      },
      "cell_type": "code",
      "source": [
        "test = model.sample_text(\n",
        "    prompt=(\n",
        "        'Is moral progress like getting '\n",
        "        'a progressively clearer picture of something true and deep?'\n",
        "    ),\n",
        "    max_tokens=5000,\n",
        ")\n",
        "print(test)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "gOdx47MmtIF7"
      },
      "cell_type": "code",
      "source": [
        "test = model.sample_choice(\n",
        "    prompt=(\n",
        "        'For Richard Rorty, is moral progress like getting '\n",
        "        'a progressively clearer picture of something true and deep?\\n'\n",
        "    ),\n",
        "    responses=('Yes', 'No'),\n",
        ")\n",
        "print(test)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "Ya7BvCG1cwNY"
      },
      "cell_type": "code",
      "source": [
        "# @title Load prefabs from packages to make the specific palette to use here.\n",
        "\n",
        "prefabs = {\n",
        "    **helper_functions.get_package_classes(entity_prefabs),\n",
        "    **helper_functions.get_package_classes(game_master_prefabs),\n",
        "}"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "TSB7w33mRMOj"
      },
      "cell_type": "code",
      "source": [
        "# @title Print menu of prefabs\n",
        "\n",
        "display.display(\n",
        "    display.Markdown(helper_functions.print_pretty_prefabs(prefabs))\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "cKefuoQzj7tA"
      },
      "cell_type": "code",
      "source": [
        "DEFAULT_INSTRUCTIONS_COMPONENT_KEY = 'Instructions'\n",
        "DEFAULT_INSTRUCTIONS_PRE_ACT_LABEL = '\\nInstructions'\n",
        "\n",
        "\n",
        "@dataclasses.dataclass\n",
        "class MyAgent(prefab_lib.Prefab):\n",
        "  \"\"\"A prefab implementing an entity with a minimal set of components.\"\"\"\n",
        "\n",
        "  description: str = (\n",
        "      'An entity that has a minimal set of components and is configurable by'\n",
        "      ' the user. The initial set of components manage memory, observations,'\n",
        "      ' and instructions. If goal is specified, the entity will have a goal '\n",
        "      'constant component.'\n",
        "  )\n",
        "  params: Mapping[str, str] = dataclasses.field(\n",
        "      default_factory=lambda: {\n",
        "          'name': 'Alice',\n",
        "      }\n",
        "  )\n",
        "\n",
        "  def build(\n",
        "      self,\n",
        "      model: language_model.LanguageModel,\n",
        "      memory_bank: basic_associative_memory.AssociativeMemoryBank,\n",
        "  ) -> entity_agent_with_logging.EntityAgentWithLogging:\n",
        "    \"\"\"Build an agent.\n",
        "\n",
        "    Args:\n",
        "      model: The language model to use.\n",
        "      memory_bank: The agent's memory_bank object.\n",
        "\n",
        "    Returns:\n",
        "      An entity.\n",
        "    \"\"\"\n",
        "\n",
        "    agent_name = self.params.get('name', 'Bob')\n",
        "\n",
        "    instructions = agent_components.instructions.Instructions(\n",
        "        agent_name=agent_name,\n",
        "        pre_act_label=DEFAULT_INSTRUCTIONS_PRE_ACT_LABEL,\n",
        "    )\n",
        "\n",
        "    observation_to_memory = agent_components.observation.ObservationToMemory()\n",
        "\n",
        "    observation_label = '\\nObservation'\n",
        "    observation = agent_components.observation.LastNObservations(\n",
        "        history_length=100, pre_act_label=observation_label\n",
        "    )\n",
        "\n",
        "    situation_representation_key = 'situation_representation'\n",
        "    situation_representation = (\n",
        "        situation_representation_via_narrative.SituationRepresentation(\n",
        "            model=model,\n",
        "            observation_component_key=(\n",
        "                agent_components.observation.DEFAULT_OBSERVATION_COMPONENT_KEY\n",
        "            ),\n",
        "            declare_entity_as_protagonist=True,\n",
        "        )\n",
        "    )\n",
        "\n",
        "    guiding_principle_key = 'principle'\n",
        "    principle = agent_components.question_of_recent_memories.QuestionOfRecentMemories(\n",
        "        model=model,\n",
        "        pre_act_label=f'{agent_name} main guiding principle:',\n",
        "        question=(\n",
        "            f'How can {agent_name} exploit the situation for personal '\n",
        "            'gain and gratification?'\n",
        "        ),\n",
        "        answer_prefix=f'{agent_name} understands that ',\n",
        "        add_to_memory=False,\n",
        "        components=[\n",
        "            DEFAULT_INSTRUCTIONS_COMPONENT_KEY,\n",
        "            situation_representation_key,\n",
        "        ],\n",
        "    )\n",
        "\n",
        "    components_of_agent = {\n",
        "        DEFAULT_INSTRUCTIONS_COMPONENT_KEY: instructions,\n",
        "        'observation_to_memory': observation_to_memory,\n",
        "        agent_components.observation.DEFAULT_OBSERVATION_COMPONENT_KEY: (\n",
        "            observation\n",
        "        ),\n",
        "        agent_components.memory.DEFAULT_MEMORY_COMPONENT_KEY: (\n",
        "            agent_components.memory.AssociativeMemory(memory_bank=memory_bank)\n",
        "        ),\n",
        "        'situation_representation': situation_representation,\n",
        "        guiding_principle_key: principle,\n",
        "    }\n",
        "\n",
        "    component_order = list(components_of_agent.keys())\n",
        "\n",
        "    act_component = agent_components.concat_act_component.ConcatActComponent(\n",
        "        model=model,\n",
        "        component_order=component_order,\n",
        "    )\n",
        "\n",
        "    agent = entity_agent_with_logging.EntityAgentWithLogging(\n",
        "        agent_name=agent_name,\n",
        "        act_component=act_component,\n",
        "        context_components=components_of_agent,\n",
        "    )\n",
        "\n",
        "    return agent"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "fEOZ0lhBtfaB"
      },
      "cell_type": "code",
      "source": [
        "prefabs['myagent__Entity'] = MyAgent()"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "NujDQ8gg9xfC"
      },
      "cell_type": "code",
      "source": [
        "# @title Generate initial conditions for the simulation\n",
        "\n",
        "YEAR = 1546\n",
        "PLACE = 'St Andrews, Scotland'\n",
        "CONTEXT = 'possible sources of conflict or drama'\n",
        "NUM_STATEMENTS = 20\n",
        "NAMES_TO_GENERATE = 10\n",
        "\n",
        "prompt = interactive_document.InteractiveDocument(model)\n",
        "unparsed_statements = prompt.open_question(\n",
        "    question=(\n",
        "        f'Generate a string of {NUM_STATEMENTS} facts about {PLACE} in '\n",
        "        f'the year {YEAR}. Pay special attention to {CONTEXT}. Write '\n",
        "        'in present tense. Separate generated facts '\n",
        "        \"with ' *** '.\"\n",
        "    ),\n",
        "    max_tokens=4500,\n",
        "    terminators=(),\n",
        ")\n",
        "statements = unparsed_statements.split('***')\n",
        "statements = [s.strip() for s in statements]\n",
        "\n",
        "for statement in statements:\n",
        "  print(statement)\n",
        "\n",
        "unparsed_names = prompt.open_question(\n",
        "    question=(\n",
        "        f'Generate a string of {NAMES_TO_GENERATE} names appropriate for this '\n",
        "        \"time and place. Include surnames. Separate them with ' *** '\"\n",
        "    ),\n",
        "    max_tokens=4500,\n",
        "    terminators=(),\n",
        ")\n",
        "names = unparsed_names.split('***')\n",
        "names = [n.strip() for n in names]\n",
        "\n",
        "PLAYER_ONE = names[0]\n",
        "PLAYER_TWO = names[1]\n",
        "\n",
        "print('\\n')\n",
        "print(f'Player one: {PLAYER_ONE}')\n",
        "print(f'Player two: {PLAYER_TWO}')\n",
        "\n",
        "prefix = f'{PLAYER_ONE} and {PLAYER_TWO} '\n",
        "premise = prompt.open_question(\n",
        "    question=(\n",
        "        f'Given the setting, why are {PLAYER_ONE} and {PLAYER_TWO} about '\n",
        "        'to interact?'\n",
        "    ),\n",
        "    answer_prefix=prefix,\n",
        ")\n",
        "premise = f'{prefix}{premise}'\n",
        "\n",
        "print('\\n')\n",
        "print(premise)\n",
        "\n",
        "player_one_context = prompt.open_question(\n",
        "    question=(\n",
        "        f'{PLAYER_ONE} has a goal or interest that, if pursued, '\n",
        "        f'would complicate things for {PLAYER_TWO}. What is it?'\n",
        "    ),\n",
        "    max_tokens=1000,\n",
        ")\n",
        "\n",
        "print('\\n')\n",
        "print(player_one_context)\n",
        "\n",
        "player_two_context = prompt.open_question(\n",
        "    question=(\n",
        "        f'{PLAYER_TWO} has a goal or interest that, if pursued, '\n",
        "        f'would complicate things for {PLAYER_ONE}. What is it?'\n",
        "    ),\n",
        "    max_tokens=1000,\n",
        ")\n",
        "\n",
        "print('\\n')\n",
        "print(player_two_context)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "bREMbhkVcwQH"
      },
      "cell_type": "code",
      "source": [
        "# @title Configure instances.\n",
        "\n",
        "instances = [\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='basic__Entity',\n",
        "        role=prefab_lib.Role.ENTITY,\n",
        "        params={\n",
        "            'name': PLAYER_ONE,\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='myagent__Entity',\n",
        "        role=prefab_lib.Role.ENTITY,\n",
        "        params={\n",
        "            'name': PLAYER_TWO,\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='generic__GameMaster',\n",
        "        role=prefab_lib.Role.GAME_MASTER,\n",
        "        params={\n",
        "            'name': 'default rules',\n",
        "            'acting_order': 'game_master_choice',\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='dialogic__GameMaster',\n",
        "        role=prefab_lib.Role.GAME_MASTER,\n",
        "        params={\n",
        "            'name': 'conversation rules',\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='formative_memories_initializer__GameMaster',\n",
        "        role=prefab_lib.Role.INITIALIZER,\n",
        "        params={\n",
        "            'name': 'initial setup rules',\n",
        "            'next_game_master_name': 'default rules',\n",
        "            'shared_memories': statements,\n",
        "            'player_specific_context': {\n",
        "                PLAYER_ONE: player_one_context,\n",
        "                PLAYER_TWO: player_two_context,\n",
        "            },\n",
        "        },\n",
        "    ),\n",
        "]"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "LvZQZ2qmcwSU"
      },
      "cell_type": "code",
      "source": [
        "config = prefab_lib.Config(\n",
        "    default_premise=premise,\n",
        "    default_max_steps=5,\n",
        "    prefabs=prefabs,\n",
        "    instances=instances,\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "dJiP0nFzdHwM"
      },
      "cell_type": "markdown",
      "source": [
        "# The simulation"
      ]
    },
    {
      "metadata": {
        "id": "6-aNTmgacwbk"
      },
      "cell_type": "code",
      "source": [
        "# @title Initialize the simulation\n",
        "runnable_simulation = simulation.Simulation(\n",
        "    config=config,\n",
        "    model=model,\n",
        "    embedder=embedder,\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "dl1wJP9EcweG"
      },
      "cell_type": "code",
      "source": [
        "# @title Run the simulation\n",
        "raw_log = []\n",
        "results_log = runnable_simulation.play(max_steps=5, raw_log=raw_log)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "oQvo3alwdMgE"
      },
      "cell_type": "code",
      "source": [
        "# @title Display the log\n",
        "display.HTML(results_log)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "S8TdyvoYdSV8"
      },
      "cell_type": "markdown",
      "source": [
        "```\n",
        "Copyright 2024 DeepMind Technologies Limited.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "you may not use this file except in compliance with the License.\n",
        "You may obtain a copy of the License at\n",
        "\n",
        "    https://www.apache.org/licenses/LICENSE-2.0\n",
        "\n",
        "Unless required by applicable law or agreed to in writing, software\n",
        "distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "See the License for the specific language governing permissions and\n",
        "limitations under the License.\n",
        "```"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "private_outputs": true,
      "provenance": [],
      "toc_visible": true
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
